package com.candy.biz.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.candy.biz.domain.entity.SysLog;
import com.candy.biz.domain.param.SysLogQueryParam;
import com.candy.biz.domain.param.SysLogSaveParam;
import com.candy.biz.domain.vo.OnlineUserVO;
import com.candy.biz.domain.vo.SysLogVO;
import com.candy.biz.manager.SysLogManager;
import com.candy.biz.repository.SysLogRepository;
import com.candy.biz.service.SysLogService;
import com.candy.common.domain.PageVO;
import com.candy.common.utils.CommonUtil;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.ExampleMatcher;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.aggregation.TypedAggregation;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * 操作日志-服务层实现。
 *
 * @author rong xi
 * @date 2023/10/11 17:54
 * @version 1.0.0
 */
@Service
@AllArgsConstructor
public class SysLogServiceImpl implements SysLogService {

    private final SysLogRepository sysLogRepository;
    private final MongoTemplate mongoTemplate;
    /**
     * 查询操作日志详情
     *
     * @param id 操作日志id
     * @return 详情VO
     */
    @Override
    public SysLogVO queryInfo(Long id) {
        return sysLogRepository.findById(id).map(SysLogManager::poToVo).orElse(null);
    }

    /**
     * 分页查询操作日志
     *
     * @param param 查询参数
     * @return 分页结果
     */
    @Override
    public PageVO<SysLogVO> queryPage(SysLogQueryParam param) {
        Pageable pageable = param.getPageable();
        SysLog sysLog = BeanUtil.copyProperties(param, SysLog.class);
        ExampleMatcher matcher = ExampleMatcher.matching()
                // 表示模糊查询
                .withStringMatcher(ExampleMatcher.StringMatcher.CONTAINING)
                // 忽略大小写
                .withIgnoreCase(true);
        Example<SysLog> userExample = Example.of(sysLog,matcher);
        Page<SysLog> page = sysLogRepository.findAll(userExample, pageable);
        return SysLogManager.pageToPageVO(page);
    }

    /**
     * 分页查询在线用户
     *
     * @param param 查询参数
     * @return 分页结果
     */
    @Override
    public PageVO<OnlineUserVO> queryOnlinePage(SysLogQueryParam param) {
        List<String> tokenList = Optional.ofNullable(StpUtil.searchTokenValue("", 0, 1000, false))
                .filter(CollectionUtil::isNotEmpty)
                .map(m->m.stream().map(s->s.split(":")[3]).collect(Collectors.toList()))
                .orElse(CollectionUtil.newArrayList());

        //组装查询条件
        String requestTimeFieldName = CommonUtil.getFieldName(SysLog::getRequestTime);
        String requestTokenFieldName = CommonUtil.getFieldName(SysLog::getRequestToken);
        String requestIpFieldName = CommonUtil.getFieldName(SysLog::getRequestIp);
        String requestRegionFieldName = CommonUtil.getFieldName(SysLog::getRequestRegion);
        String userIdFieldName = CommonUtil.getFieldName(SysLog::getUserId);
        String userAccountFieldName = CommonUtil.getFieldName(SysLog::getUserAccount);
        String userNameFieldName = CommonUtil.getFieldName(SysLog::getUserName);
        String clientBrowserFieldName = CommonUtil.getFieldName(SysLog::getClientBrowser);
        String clientOsFieldName = CommonUtil.getFieldName(SysLog::getClientOs);

        Criteria criteria = Criteria.where(requestTimeFieldName).gte(LocalDateTime.now().minusMinutes(10));
        criteria.and(StrUtil.toCamelCase(requestTokenFieldName)).in(tokenList);
        CommonUtil.predicateRunnable(StrUtil.isNotBlank(param.getRequestToken()),() -> criteria.and(requestTokenFieldName).is(param.getRequestToken()));
        CommonUtil.predicateRunnable(StrUtil.isNotBlank(param.getUserAccount()),() -> criteria.and(userAccountFieldName).is(param.getUserAccount()));
        CommonUtil.predicateRunnable(StrUtil.isNotBlank(param.getRequestIp()),() -> criteria.and(requestIpFieldName).regex(".*"+param.getRequestIp()+".*"));

        String[] projectFields = {requestTokenFieldName,requestIpFieldName,requestRegionFieldName,userIdFieldName,userAccountFieldName,userNameFieldName,clientBrowserFieldName,clientOsFieldName};
        String[] allFields = ArrayUtil.append(projectFields,requestTimeFieldName);
        TypedAggregation<SysLog> aggregation = Aggregation.newAggregation(SysLog.class,
                //查询条件
                Aggregation.match(criteria),
                //分组条件
                Aggregation.group(projectFields)
                        .first(requestTokenFieldName).as(requestTokenFieldName)
                        .first(userIdFieldName).as(userIdFieldName)
                        .first(userAccountFieldName).as(userAccountFieldName)
                        .first(userNameFieldName).as(userNameFieldName)
                        .first(requestIpFieldName).as(requestIpFieldName)
                        .first(requestRegionFieldName).as(requestRegionFieldName)
                        .first(clientOsFieldName).as(clientOsFieldName)
                        .first(clientBrowserFieldName).as(clientBrowserFieldName)
                        .max(requestTimeFieldName).as(requestTimeFieldName),
                //排序
                Aggregation.sort(param.getSort()),
                //查询字段

                Aggregation.project(allFields),
                //跳到第几个开始
                Aggregation.skip((param.getPageable().getOffset())),
                // 查出多少个数据
                Aggregation.limit(param.getPageSize().longValue())
        );
        AggregationResults<OnlineUserVO> results = mongoTemplate.aggregate(aggregation,OnlineUserVO.class);

        TypedAggregation<SysLog> totalAggregation = Aggregation.newAggregation(SysLog.class,
                //查询条件
                Aggregation.match(criteria),
                //分组条件
                Aggregation.group(projectFields)
                        .first(requestTokenFieldName).as(requestTokenFieldName)
                        .first(userIdFieldName).as(userIdFieldName)
                        .first(userAccountFieldName).as(userAccountFieldName)
                        .first(userNameFieldName).as(userNameFieldName)
                        .first(requestIpFieldName).as(requestIpFieldName)
                        .first(requestRegionFieldName).as(requestRegionFieldName)
                        .first(clientOsFieldName).as(clientOsFieldName)
                        .first(clientBrowserFieldName).as(clientBrowserFieldName)
                        .max(requestTimeFieldName).as(requestTimeFieldName),
                Aggregation.project(allFields)

        //允许在聚合操作中使用磁盘空间
        ).withOptions(Aggregation.newAggregationOptions().allowDiskUse(true).build());
        AggregationResults<OnlineUserVO> totalResults = mongoTemplate.aggregate(totalAggregation, OnlineUserVO.class);

        return PageVO.of(results.getMappedResults(),(long)totalResults.getMappedResults().size());
    }



    /**
     * 根据查询条件导出操作日志。
     *
     * @param param 查询条件
     */
    @Override
    @SneakyThrows
    public void export(SysLogQueryParam param) {
        //修改分页条件
        param.preparationExport();
        //查询数据
        List<SysLogVO> rows = queryPage(param).getList();
        //导出文件
        CommonUtil.exportExcel("操作日志",rows,SysLogVO.class);
    }


    /**
     * 保存操作日志
     *
     * @param param 保存参数
     * @return 保存结果
     */
    @Override
    public Boolean saveHandle(SysLogSaveParam param) {
        CommonUtil.predicateRunnable(ObjectUtil.isNull(param.getId()),
                ()->sysLogRepository.save(SysLogManager.insertPrepare(param)),
                ()->sysLogRepository.save(SysLogManager.updatePrepare(param)));
        return true;
    }

    /**
     * 强退用户
     * @param token 用户token
     * @return 强退结果
     */
    @Override
    public Boolean tickOutUser(String token) {
        StpUtil.logoutByTokenValue(token);
        StpUtil.kickoutByTokenValue(token);
        return true;
    }
}
